home *** CD-ROM | disk | FTP | other *** search
/ Libris Britannia 4 / science library(b).zip / science library(b) / DDJMAG / DDJ9006.ZIP / WEEKS.LST < prev   
File List  |  1990-05-02  |  54KB  |  1,635 lines

  1. _C++ FILE OBJECTS_
  2. by Kevin Weeks
  3.  
  4. [LISTING ONE]
  5.  
  6. /* FILETEST.HPP Written by Kevin D. Weeks Released to the Public Domain  */
  7.  
  8. #ifndef FILESPEC_HPP                        // prevent multiple #includes
  9. #define FILESPEC_HPP
  10.  
  11. #include <stdio.h>
  12.  
  13. #define ERR     -1
  14. // create a boolean type
  15. typedef enum{FALSE,TRUE}     bool;
  16.  
  17. // specify attribute sizes
  18. #define SIZE_DEVICE 2
  19. #define SIZE_PREFIX 64
  20. #define SIZE_NAME   9                       // the dot is part of the name
  21. #define SIZE_SUFFIX 3
  22.  
  23. // these constants are used as flags in the condition attribute
  24. #define FLAG_DEVICE     0x0001
  25. #define FLAG_PREFIX     0x0002
  26. #define FLAG_NAME       0x0004
  27. #define FLAG_SUFFIX     0x0008
  28. #define INCOMPLETE      0x000f
  29. #define INVALID_CHAR    0x00f0
  30. #define READ_ONLY       0x0100
  31.  
  32. class   File_Spec
  33. {
  34.     // first define the attributes
  35.     char    device[SIZE_DEVICE + 1];        // under MS-DOS, the disk drive
  36.     char    *prefix;                        //   "     "   , the path
  37.     char    name[SIZE_NAME + 1];            //   "     "   , still the name
  38.     char    suffix[SIZE_SUFFIX + 1];        //   "     "   , the extension
  39.     char    *request;                       // pointer to response string for
  40.                                             // get_XXXX() methods
  41.     int     prefix_length;
  42.     int     request_length;
  43.     unsigned int    condition;              // current status of the object
  44.  
  45.     // and then the private methods
  46.     bool    check_prefix(void);             // determine completeness of prefix
  47.     bool    parse_prefix(void);             // interpret relative prefix
  48.     bool    check_chars(char *string, unsigned int attrib_flag);
  49.                                             // test for valid MS-DOS file chars
  50.     void    copy(const File_Spec& original);  // copy another file spec
  51.     bool    clear_attribute(char *attribute, unsigned int attrib_flag);
  52.     bool    realloc(char **pointer, int *length, int new_length);
  53.  
  54.     // make class File_Spec a friend of itself
  55.     friend  class File_Spec;
  56.  
  57.   // now the public methods
  58.   public:
  59.     // construct file specifications
  60.             File_Spec(void);
  61.             File_Spec(const char *file);
  62.             File_Spec(const File_Spec& original);
  63.  
  64.     // destroy a file specification
  65.             ~File_Spec(void);
  66.  
  67.     // return status of object
  68.     unsigned int    status(void);
  69.  
  70.     // extend the language by overloading the = operator
  71.     File_Spec   operator=(const File_Spec& original);
  72.  
  73.     // ALL get_XXXX() methods guarantee to return NUL-terminated strings
  74.     char    *get_device(void);
  75.     char    *get_prefix(void);
  76.     char    *get_name(void);
  77.     char    *get_suffix(void);
  78.     char    *filespec(void);
  79.  
  80.     // ALL change_XXXX() methods guarantee to copy no more than SIZE_n
  81.     // characters from the pass parameter
  82.     bool    change_device(const char *string = NULL);
  83.     bool    change_prefix(const char *string = NULL);
  84.     bool    change_name(const char *string = NULL);
  85.     bool    change_suffix(const char *string = NULL);
  86.  
  87.     // disables/enables change_XXXX()
  88.     void    read_only(bool flag);
  89.  
  90.     // attempt to complete the file specification
  91.     bool    complete(void);
  92. };
  93.  
  94. #endif
  95.  
  96.  
  97.  
  98. [LISTING TWO]
  99.  
  100. /* FILESPEC.CPP Written by Kevin D. Weeks Released to the Public Domain  */
  101.  
  102. #include <stdio.h>
  103. #include <errno.h>
  104. #include <string.h>
  105. #include <ctype.h>
  106. #include "filespec.hpp"
  107.  
  108. // this declaration instructs the compiler to NOT perform name-mangling
  109. // on these functions.
  110. extern "C"
  111. {
  112.     extern char     ll_get_drive(void);
  113.     extern int      ll_get_cwd(int, char *);
  114.     extern unsigned int ll_write(int, unsigned int, const void *);
  115. }
  116.  
  117. // these constants are states used in parsing the file string
  118. #define DEVICE  1
  119. #define PREFIX  2
  120. #define NAME    3
  121. #define SUFFIX  4
  122.  
  123. // a macro to return out of memory errors
  124. #define MEM_ERR(length) { errno = ENOMEM; length = 0; return NULL; }
  125.  
  126. extern volatile int errno;
  127.  
  128. /*  This final File_Spec constructor is passed a character string which it
  129.     attempts to parse into its various components. The parsing is done with
  130.     a finite state machine that begins at the end of the string and backs
  131.     up to the beginning, changing state as it encounters the element delimiters
  132.     ':', '.', and '\'. Once the string has been parsed the components are
  133.     checked for completeness and for the validity of the file characters.
  134.     In order to correctly interpret a string with a prefix but no name the
  135.     string must end with a '\'. Also note that any individual component
  136.     device, name, etc.) that is too long is truncated to a legal length.
  137. */
  138. File_Spec::File_Spec(const char *file)
  139. {
  140.     char    *tmp_file;                  // local copy of tmp_file
  141.     char    *tmp_prefix;                // temporary string for the prefix
  142.     int     pos;                        // current position in tmp_file string
  143.     int     state;                      // current state
  144.     int     i;                          // trash variable
  145.     // initialize everything that needs initializing
  146.     prefix = NULL;
  147.     prefix_length = 0;
  148.     request_length = 0;
  149.     condition = 0;
  150.     device[0] = device[SIZE_DEVICE] = '\0';
  151.     name[0] = name[SIZE_NAME] = '\0';
  152.     suffix[0] = suffix[SIZE_SUFFIX] = '\0';
  153.     errno = 0;
  154.     if (file == NULL || *file == '\0')
  155.     {
  156.         condition = INCOMPLETE;
  157.         return;
  158.     }
  159.     if ((tmp_file = new char[strlen(file) + 1]) == NULL)
  160.     {
  161.         errno = ENOMEM;
  162.         return;
  163.     }
  164.     strcpy(tmp_file,file);
  165.     if ((tmp_prefix = new char[SIZE_PREFIX + 1]) == NULL)
  166.     {
  167.         errno = ENOMEM;
  168.         return;
  169.     }
  170.     tmp_prefix[0] = tmp_prefix[SIZE_PREFIX] = '\0';
  171.     pos = strlen(tmp_file) - 1;             // set pos to last character
  172.     // this while loop is the finite state machine mentioned above. note
  173.     // that the strncpy() calls copy everthing from just beyond the
  174.     // character that satisfies the case, and then the tmp_file is truncated
  175.     // at that point with a '\0'.
  176.     state = SUFFIX;
  177.     do
  178.     {
  179.         switch (tmp_file[pos])
  180.         {
  181.             case '.':
  182.                 // a dot only counts in the SUFFIX state
  183.                 if (state == SUFFIX)
  184.                 {
  185.                     strncpy(suffix,&tmp_file[pos + 1],SIZE_SUFFIX);
  186.                     tmp_file[pos + 1] = '\0';
  187.                     state = NAME;
  188.                 }
  189.                 else
  190.                     if (state == NAME)
  191.                         // this means we've got two or more dots in a name
  192.                         // which is illegal. flag it as an invalid char
  193.                         condition |= FLAG_NAME << 4;
  194.                 break;
  195.             case '\\':
  196.                 if ((state == SUFFIX) || (state == NAME))
  197.                 {
  198.                     strncpy(name,&tmp_file[pos + 1],SIZE_NAME);
  199.                     tmp_file[pos + 1] = '\0';
  200.                     state = PREFIX;
  201.                 }
  202.                 break;
  203.             case ':':
  204.                 if ((state == SUFFIX) || (state == NAME))
  205.                 {
  206.                     strncpy(name,&tmp_file[pos + 1],SIZE_NAME);
  207.                     tmp_file[pos + 1] = '\0';
  208.                     state = DEVICE;
  209.                 }
  210.                 else
  211.                     if (state == PREFIX)
  212.                     {
  213.                         strncpy(tmp_prefix,&tmp_file[pos + 1],SIZE_PREFIX);
  214.                         tmp_file[pos + 1] = '\0';
  215.                         state = DEVICE;
  216.                     }
  217.                 break;
  218.         }
  219.         --pos;                              // go to next character
  220.     } while(pos >= 0);
  221.     // now resolve whatever state we ended up in
  222.     if ((state == SUFFIX) || (state == NAME))
  223.         strncpy(name,tmp_file,SIZE_NAME);
  224.     else
  225.         if (state == PREFIX)
  226.             strncpy(tmp_prefix,tmp_file,SIZE_PREFIX);
  227.         else
  228.             strncpy(device,tmp_file,SIZE_DEVICE);
  229.     // validate the device
  230.     device[1] = ':';
  231.     device[2] = '\0';
  232.     if (device[0] == '\0')
  233.         condition |= FLAG_DEVICE;
  234.     else
  235.     {
  236.         // make the device upper-case for simplicity's sake later on
  237.         device[0] = toupper(device[0]);
  238.         if (device[0] < 'A' || device[0] > 'Z')
  239.             condition |= FLAG_DEVICE << 4;
  240.     }
  241.     // use the existing change_prefix() method to create the prefix and
  242.     // validate it
  243.     change_prefix(tmp_prefix);
  244.     delete[SIZE_PREFIX + 1] tmp_prefix;
  245.     // now validate the name
  246.     if (name[0] == '\0')
  247.         condition |= FLAG_NAME;
  248.     else
  249.     {
  250.         if (name[0] == '.')
  251.         {
  252.             condition |= FLAG_NAME;
  253.             name[0] = '\0';
  254.         }
  255.         else
  256.             if (check_chars(name,FLAG_NAME))
  257.             {
  258.                 // as far as we're concerned name HAS to end with a dot.
  259.                 i = strlen(name);
  260.                 if (name[i - 1] != '.')
  261.                 {
  262.                     if (i == SIZE_NAME)
  263.                         i = SIZE_NAME - 1;
  264.                     name[i++] = '.';
  265.                     name[i] = '\0';
  266.                 }
  267.             }
  268.     }
  269.     // and suffix
  270.     if (suffix[0] != '\0')
  271.         check_chars(suffix,FLAG_SUFFIX);
  272.  
  273. }
  274. /*  This constructor creates an empty object suitable for later filling. */
  275. File_Spec::File_Spec(void)
  276. {
  277.     // Set both ends of device, name, and suffix to NUL. Since strncpy()
  278.     // is used later on this guarantees these three are always NUL-terminated.
  279.     device[0] = device[SIZE_DEVICE] = '\0';
  280.     name[0] = name[SIZE_NAME] = '\0';
  281.     suffix[0] = suffix[SIZE_SUFFIX] = '\0';
  282.     prefix = NULL;
  283.     prefix_length = 0;
  284.     request = NULL;
  285.     request_length = 0;
  286.     condition = INCOMPLETE;                 // everthing's incomplete
  287. }
  288. /* The so-called "copy" constructor actually calls a copy() method after
  289.     doing some preliminary initialization.
  290. */
  291. File_Spec::File_Spec(const File_Spec& original)
  292. {
  293.     prefix = NULL;
  294.     prefix_length = 0;
  295.     request = NULL;
  296.     request_length = 0;
  297.     copy(original);
  298. }
  299. /* The destructor simply releases the memory, if any, assigned to prefix
  300.     and request.
  301. */
  302. File_Spec::~File_Spec(void)
  303. {
  304.     if (prefix != NULL)
  305.     {
  306.         delete[prefix_length] prefix;
  307.         prefix = NULL;
  308.         prefix_length = 0;
  309.     }
  310.     if (request != NULL)
  311.     {
  312.         delete[request_length] request;
  313.         request = NULL;
  314.         request_length = 0;
  315.     }
  316. }
  317. /* Tell 'em how we're doing */
  318. unsigned int    File_Spec::status(void)
  319. {
  320.     return(condition);
  321. }
  322. /* This method's purpose is to return a string containing a complete file
  323.     specification string for use by clients.
  324. */
  325. char    *File_Spec::filespec(void)
  326. {
  327.     int     length;
  328.     // first calculate the length of the file specification
  329.     length = strlen(device);
  330.     length += strlen(prefix);
  331.     length += strlen(name);
  332.     // + 2 to allow for the NUL-terminator and the colon
  333.     length += strlen(suffix) + 2;
  334.     // if request isn't already long enough then de-allocate the current
  335.     // pointer and allocate a new one
  336.     if (request_length < length)
  337.         if (!realloc(&request,&request_length,length))
  338.             return(NULL);
  339.     // build the string
  340.     strcpy(request,device);
  341.     if (prefix != NULL)
  342.         strcat(request,prefix);
  343.     strcat(request,name);
  344.     strcat(request,suffix);
  345.     return(request);
  346. }
  347. /*  get_device(), get_prefix(), get_name(), and get_suffix() are all essen-
  348.     tialy alike. if the request string isn't long enough then it is re-
  349.     allocated, then the attribute that was requested is copied into request.
  350. */
  351. char    *File_Spec::get_device(void)
  352. {
  353.     errno = 0;
  354.     if (request_length < SIZE_DEVICE + 1)
  355.         if (!realloc(&request,&request_length,SIZE_DEVICE + 1))
  356.             return(NULL);
  357.     strcpy(request,device);
  358.     return(request);
  359. }
  360. /*  Returning the prefix is a bit more complicated than the other get
  361.     routines.
  362. */
  363. char    *File_Spec::get_prefix(void)
  364. {
  365.     errno = 0;
  366.     if (prefix_length)
  367.     {
  368.         if (request_length < prefix_length)
  369.             if (!realloc(&request,&request_length,prefix_length))
  370.                 return(NULL);
  371.         strcpy(request,prefix);
  372.     }
  373.     else
  374.     // even if the prefix is NULL we promised to return something. Here,
  375.     // a string 1 character long consisting of a NUL-terminator
  376.     {
  377.         if (request_length == 0)
  378.             if (realloc(&request,&request_length,1))
  379.                 *request = '\0';
  380.     }
  381.     return(request);
  382. }
  383. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  384. char    *File_Spec::get_name(void)
  385. {
  386.     errno = 0;
  387.     if (request_length < SIZE_NAME + 1)
  388.         if (!realloc(&request,&request_length,SIZE_NAME + 1))
  389.             return(NULL);
  390.     strcpy(request,name);
  391.     return(request);
  392. }
  393. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  394. char    *File_Spec::get_suffix(void)
  395. {
  396.     errno = 0;
  397.     if (request_length < SIZE_SUFFIX + 1)
  398.         if (!realloc(&request,&request_length,SIZE_SUFFIX + 1))
  399.             return(NULL);
  400.     strcpy(request,suffix);
  401.     return(request);
  402. }
  403. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
  404.     as with the get_XXXX() methods above, change_device(), change_prefix(),
  405.     change_name(), and change_suffix() are basically the same. if the
  406.     current condition is "read-only" then return a FALSE. if a NULL string
  407.     is passed (note the default) the current object is truncated and the
  408.     corresponding incomplete flag is set. otherwise SIZE_n characters are
  409.     copied and the standard validity checks are made.
  410.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  411. bool    File_Spec::change_device(const char *string)
  412. {
  413.     if (condition & READ_ONLY)
  414.         return(FALSE);
  415.     if (string == NULL || *string == '\0')
  416.         return(clear_attribute(device,FLAG_DEVICE));
  417.     strncpy(device,string,SIZE_DEVICE);
  418.     device[0] = toupper(device[0]);
  419.     device[1] = ':';
  420.     device[2] = '\0';
  421.     if (device[0] < 'A' || device[0] > 'Z')
  422.     {
  423.         condition |= FLAG_DEVICE << 4;
  424.         return(FALSE);
  425.     }
  426.     else
  427.         condition &= ~(FLAG_DEVICE << 4);
  428.     condition &= ~FLAG_DEVICE;
  429.     return(TRUE);
  430. }
  431. /*  get_prefix(), like change_prefix(), is somewhat more complicated than the
  432.     other change routines
  433. */
  434. bool    File_Spec::change_prefix(const char *string)
  435. {
  436.     int     new_length;
  437.     if (condition & READ_ONLY)
  438.         return(FALSE);
  439.     if (string == NULL || *string == '\0')
  440.         return(clear_attribute(prefix,FLAG_PREFIX));
  441.     errno = 0;
  442.     // get the size of the new prefix and if the existing prefix isn't long
  443.     // enough then re-allocate it
  444.     new_length = strlen(string);
  445.     if (new_length > SIZE_PREFIX)
  446.         new_length = SIZE_PREFIX;
  447.     if (prefix_length < new_length + 1)
  448.         if (!realloc(&prefix,&prefix_length,new_length + 1))
  449.             return(FALSE);
  450.     // copy in the new string and validate it.
  451.     strncpy(prefix,string,new_length);
  452.     prefix[new_length] = '\0';
  453.     if (check_chars(prefix,FLAG_PREFIX) == FALSE)
  454.         return(FALSE);
  455.     return(check_prefix());
  456. }
  457. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  458. bool    File_Spec::change_name(const char *string)
  459. {
  460.     int     i;
  461.     if (condition & READ_ONLY)
  462.         return(FALSE);
  463.     if (string == NULL || *string == '\0')
  464.         return(clear_attribute(name,FLAG_NAME));
  465.     i = 0;
  466.     while (string[i])
  467.     {
  468.         if (string[i] == '.')
  469.         {
  470.             change_suffix(&string[i + 1]);
  471.             if (i == 0)
  472.             {
  473.                 name[0] = '\0';
  474.                 condition |= FLAG_NAME;
  475.                 condition &= ~(FLAG_NAME << 4);
  476.                 return(FALSE);
  477.             }
  478.             ++i;
  479.             break;
  480.         }
  481.         if (string[i] == ':' || string[i] == '\\')
  482.         {
  483.             condition |= FLAG_NAME << 4;
  484.             return(FALSE);
  485.         }
  486.         if (++i == SIZE_NAME)
  487.             break;
  488.     }
  489.     strncpy(name,string,i);
  490.     name[i] = '\0';
  491.     if (check_chars(name,FLAG_NAME) == FALSE)
  492.         return(FALSE);
  493.     i = strlen(name);
  494.     // as far as we're concerned name HAS to end with a dot.
  495.     if (name[i - 1] != '.')
  496.     {
  497.         if (i == SIZE_NAME)
  498.             i = SIZE_NAME - 1;
  499.         name[i++] = '.';
  500.         name[i] = '\0';
  501.     }
  502.     condition &= ~FLAG_NAME;
  503.     return(TRUE);
  504. }
  505. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  506. bool    File_Spec::change_suffix(const char *string)
  507. {
  508.     if (condition & READ_ONLY)
  509.         return(FALSE);
  510.     if (string == NULL)
  511.     {
  512.         clear_attribute(suffix,FLAG_SUFFIX);
  513.         condition &= ~FLAG_SUFFIX;          // unset the incomplete suffix flag
  514.     }
  515.     if (*string == '.')
  516.     {
  517.         ++string;
  518.         strncpy(suffix,string,SIZE_SUFFIX);
  519.     }
  520.     else
  521.         strncpy(suffix,string,SIZE_SUFFIX);
  522.     if (check_chars(suffix,FLAG_SUFFIX) == FALSE)
  523.         return(FALSE);
  524.     condition &= ~FLAG_SUFFIX;
  525.     return(TRUE);
  526. }
  527. /*  This method determines whether or not the prefix is complete. */
  528. bool    File_Spec::check_prefix(void)
  529. {
  530.     int     i;
  531.  
  532.     // if the 1st character isn't a '\' then the prefix is relative to the
  533.     // current working directory.
  534.     if (prefix[0] != '\\')
  535.     {
  536.         condition |= FLAG_PREFIX;
  537.         return(FALSE);
  538.     }
  539.     i = 0;
  540.     // this loop checks for the presence of a dot followed by another dot
  541.     // or a dot followed by a backslash. either one indicates the prefix
  542.     // is relative the the current working directory.
  543.     while (prefix[i + 1])
  544.     {
  545.         if ((prefix[i] == '.') &&
  546.           (prefix[i + 1] == '\\' || prefix[i + 1] == '.'))
  547.             {
  548.                 condition |= FLAG_PREFIX;
  549.                 return(FALSE);
  550.             }
  551.         if (++i == SIZE_PREFIX - 1)
  552.             break;
  553.     }
  554.     // a prefix HAS to end with a '\'
  555.     if (prefix[i] != '\\')
  556.     {
  557.         prefix[i++] = '\\';
  558.         prefix[i] = '\0';
  559.     }
  560.     condition &= ~FLAG_PREFIX;
  561.     return(TRUE);
  562. }
  563. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  564. void    File_Spec::read_only(bool flag)
  565. {
  566.     if (flag)
  567.         condition |= READ_ONLY;
  568.     else
  569.         condition &= ~READ_ONLY;
  570. }
  571. /* This method actually attempts to complete a file specification. */
  572. bool    File_Spec::complete(void)
  573. {
  574.     char    *tstr;
  575.     char    drive;
  576.     if (condition & READ_ONLY)
  577.         return(FALSE);
  578.     // an invalid character in any of the components is an automatic failure
  579.     if (condition & INVALID_CHAR)
  580.         return(FALSE);
  581.     // no name is also an automatic failure
  582.     if (condition & FLAG_NAME)
  583.         return(FALSE);
  584.     // if no device specified then get the current drive
  585.     if (condition & FLAG_DEVICE)
  586.     {
  587.         drive = ll_get_drive();
  588.         device[0] = drive + 'A';
  589.         device[1] = ':';
  590.         device[2] = '\0';
  591.         condition &= ~FLAG_DEVICE;
  592.     }
  593.     // if the prefix isn't complete call parse_prefix()
  594.     if (condition & FLAG_PREFIX)
  595.         if (parse_prefix() == FALSE)
  596.             return(FALSE);
  597.     // everything's alright so give the client the go-ahead
  598.     condition = 0;
  599.     return(TRUE);
  600. }
  601. /*  Looks rather un-impressive doesn't it. */
  602. File_Spec   File_Spec::operator=(const File_Spec& original)
  603. {
  604.     copy(original);
  605. }
  606. /*  As with the (char *string) constructor above, this routine uses a finite
  607.     state machine to produce a complete path. the existing prefix (if any)
  608.     is processed front to back while the current working directory (cwd) is
  609.     processed back to front. the end result is that the partial prefix is
  610.     appended at the correct point to the cwd.
  611. */
  612. bool    File_Spec::parse_prefix(void)
  613. {
  614.     int     prefix_elem;
  615.     int     cwd_elem;
  616.     int     state;
  617.     char    *cwd;
  618.     errno = 0;
  619.     // set the defaults
  620.     if ((cwd = new char[SIZE_PREFIX + 1]) == NULL)
  621.     {
  622.         errno = ENOMEM;
  623.         return(FALSE);
  624.     }
  625.     // since the directory returned by ll_get_cwd() doesn't begin with a
  626.     // '\' we'll start by adding one to the beginning of cwd
  627.     cwd[0] = '\\';
  628.     cwd[1] = '\0';
  629.     // get the current working directory for the specified drive
  630.     if (ll_get_cwd(device[0] - 64,&cwd[1]) == ERR)
  631.     {
  632.         delete[SIZE_PREFIX + 1] cwd;
  633.         return(FALSE);
  634.     }
  635.     // DOS doesn't append a '\' either so we will
  636.     cwd_elem = strlen(cwd);
  637.     if (cwd[1] != '\0')
  638.     {
  639.         cwd[cwd_elem] = '\\';
  640.         cwd[cwd_elem + 1] = '\0';
  641.     }
  642.     // if there was no prefix, there is now. assign it and return
  643.     if (prefix == NULL)
  644.     {
  645.         prefix_length = SIZE_PREFIX + 1;
  646.         prefix = cwd;
  647.         return(TRUE);
  648.     }
  649.     prefix_elem = 0;
  650.     state = 0;
  651.     do
  652.     {
  653.         switch(state)
  654.         {
  655.             case 0:
  656.                 if (prefix[prefix_elem] == '.')
  657.                     // a dot means check for another dot or a '\'. goto state 1
  658.                     state = 1;
  659.                 else
  660.                     // DOS would give you a fit over this. since we're here,
  661.                     // check_prefix() found a relative component in the path.
  662.                     // however, the initial '\' means "start at the root".
  663.                     // so we go to the root by setting cwd_elem to zero and
  664.                     // start looking for a dot.
  665.                     if (prefix[prefix_elem] == '\\')
  666.                     {
  667.                         state = 1;
  668.                         cwd_elem = 0;
  669.                     }
  670.                     else
  671.                     {
  672.                         // our current character IS a character so get ready to
  673.                         // append it to cwd by going to state 3 and backing up
  674.                         // so we don't lose it.
  675.                         state = 3;
  676.                         --prefix_elem;
  677.                     }
  678.                 break;
  679.             case 1:                         // we have seen a dot (or a '\')
  680.                 if (prefix[prefix_elem] == '.')
  681.                     // another dot means go up a directory. enter state 2.
  682.                     state = 2;
  683.                 else
  684.                     // a '\' means stay here. get ready to append to the
  685.                     // cwd and enter state 3.
  686.                     if(prefix[prefix_elem] == '\\')
  687.                         state = 3;
  688.                     else
  689.                         // a character here means we just saw something like
  690.                         // .s - remain in the current directory and pass the
  691.                         // buck back to state 0.
  692.                         state = 0;
  693.                 break;
  694.             case 2:                         // two (or more) dots in a row
  695.                 if (prefix[prefix_elem] == '\\')
  696.                 {
  697.                     if (cwd_elem > 0)
  698.                         do
  699.                         {
  700.                             --cwd_elem;
  701.                         } while (cwd[cwd_elem] != '\\');
  702.                 }
  703.                 else
  704.                     // more than two dots in a row. maintain current state
  705.                     if (prefix[prefix_elem] == '.')
  706.                         break;
  707.                     else
  708.                         // this means we're seeing a "..s" type situation.
  709.                         // treat it as a "..\s" and back up one so we don't
  710.                         // lose the prefix character.
  711.                         --prefix_elem;
  712.                 state = 3;
  713.                 break;
  714.             case 3:                         // append the prefix to the cwd
  715.                 if (prefix[prefix_elem] == '.')
  716.                     // whoops, another dot. go back to state 1
  717.                     state = 1;
  718.                 else
  719.                     // more than one '\' in a row. don't change state.
  720.                     if (prefix[prefix_elem] == '\\')
  721.                         break;
  722.                     else
  723.                     {
  724.                         // the order of element increments is a bit peculiar
  725.                         // but remember we've been moving in opposite
  726.                         // directions
  727.                         do
  728.                         {
  729.                             ++cwd_elem;
  730.                             cwd[cwd_elem] = prefix[prefix_elem];
  731.                             ++prefix_elem;
  732.                         } while (prefix[prefix_elem] != '\\');
  733.                         ++cwd_elem;
  734.                         cwd[cwd_elem] = prefix[prefix_elem];
  735.                     }
  736.                 break;
  737.         };
  738.         ++prefix_elem;
  739.     } while (prefix[prefix_elem]);
  740.     cwd[++cwd_elem] = '\0';
  741.     // it worked! reset the prefix pointer and get out
  742.     delete[prefix_length] prefix;
  743.     prefix = cwd;
  744.     prefix_length = SIZE_PREFIX + 1;
  745.     return(TRUE);
  746. }
  747. /* This routine checks for valid DOS file name characters. */
  748. bool    File_Spec::check_chars(char *string, unsigned int attrib_flag)
  749. {
  750.     while (*string)
  751.     {
  752.         if ((*string < '!') ||
  753.             (*string == '"') ||
  754.             (*string > ')' && *string < '-') ||
  755.             (*string == '/') ||
  756.             (*string > '9' && *string < '@') ||
  757.             (*string == '[') ||
  758.             (*string > '\\' && *string < '^') ||
  759.             (*string == '|'))
  760.         {
  761.             condition |= attrib_flag << 4;
  762.             return(FALSE);
  763.         }
  764.         ++string;
  765.     }
  766.     condition &= ~(attrib_flag << 4);
  767.     return(TRUE);
  768. }
  769. /* Since two methods (the 2nd constructor and the "=" operator) need to
  770.     make copies of other File_Spec instances, this private method is provided
  771.     to avoid duplicating the code. this method is also the main reason for
  772.     making File_Spec a friend of itself.
  773. */
  774. void    File_Spec::copy(const File_Spec& original)
  775. {
  776.     errno = 0;
  777.     strcpy(device,original.device);
  778.     if (prefix_length < original.prefix_length)
  779.         if (!realloc(&prefix,&prefix_length,original.prefix_length))
  780.             return;
  781.     strcpy(prefix,original.prefix);
  782.     strcpy(name,original.name);
  783.     strcpy(suffix,original.suffix);
  784.     condition = original.condition;
  785. }
  786. /*  clear_attribute sets the first element of the attribute it is passed to
  787.     a NUL and then resets the condition flags.
  788. */
  789. bool    File_Spec::clear_attribute(char *attribute, unsigned int attrib_flag)
  790. {
  791.     if (attribute != NULL)
  792.         *attribute = '\0';
  793.     condition |= attrib_flag;
  794.     condition &= ~(attrib_flag << 4);
  795.     return(TRUE);
  796. }
  797. /* realloc() is used by the request and prefix attributes when they're changed
  798.     to see if they require re-allocating and to handle the re-allocation
  799.     if needed.
  800. */
  801. bool    File_Spec::realloc(char **pointer, int *length, int new_length)
  802. {
  803.     if (*length)
  804.         delete[*length] *pointer;
  805.     if ((*pointer = new char[new_length]) == NULL)
  806.     {
  807.         *length = 0;
  808.         *pointer = NULL;
  809.         return(FALSE);
  810.     }
  811.     *length = new_length;
  812.     return(TRUE);
  813. }
  814.  
  815.  
  816.  
  817. [LISTING THREE]
  818.  
  819. /* FILE.HPP Written by Kevin D. Weeks Released to the Public Domain  */
  820.  
  821. #ifndef FILE_HPP                            // prevent multiple #includes
  822. #define FILE_HPP
  823.  
  824. #include "filespec.hpp"
  825.  
  826. // file access mode definitions
  827. #define F_RDONLY    0x0000
  828. #define F_WRONLY    0x0001
  829. #define F_RDWR      0x0002
  830. #define F_COMPAT    0x0000
  831. #define F_DENYALL   0x0010
  832. #define F_DENYWR    0x0020
  833. #define F_DENYRD    0x0030
  834. #define F_DENYNO    0x0040
  835.  
  836. // flag to determine whether reads and writes have a side-effect on the
  837. // file pointer
  838. #define F_ADVANCE   0x0100
  839.  
  840. class   File: public File_Spec
  841. {
  842.     // class attributes
  843.     int         handle;                     // DOS file handle
  844.     int         open_flags;                 // flags used to open or create
  845.     long        file_pos;                   // DOS file position
  846.     long        filelength;                 // length of file
  847.   // public class methods
  848.   public:
  849.     // constructors for file objects
  850.             File(void);
  851.             File(const File_Spec& original, bool open_flag = FALSE);
  852.             File(const char *name, bool open_flag = FALSE);
  853.     // destroy the object
  854.             ~File(void);
  855.     bool    exists(void);                   // see if the file exists
  856.     bool    create(int mode_flags = 2, bool exclusive = TRUE);
  857.     bool    open(int mode_flags = 2);
  858.     bool    close(void);
  859.     unsigned int    read(void *buffer, unsigned int size);
  860.     // guarantees to write size bytes or fail
  861.     bool    write(const void *buffer, unsigned int size);
  862.     bool    truncate(void);              // truncate file at current position
  863.     // guarantees to position file pointer within file or fail
  864.     bool    set_position(long new_file_pos);
  865.     long    get_position(void);
  866.     long    size(void);
  867.     bool    rename(const char *newname);
  868.     bool    erase(void);
  869.     File    *copy(const char *newfile, bool overwrite = FALSE);
  870. };
  871. #endif
  872.  
  873.  
  874. [LISTING FOUR]
  875.  
  876. /* FILE.CPP Written by Kevin D. Weeks Released to the Public Domain  */
  877.  
  878. #include <errno.h>
  879. #include <io.h>
  880. #include <sys\stat.h>
  881. #include <dos.h>
  882. #include "file.hpp"
  883.  
  884. // this declaration instructs the compiler to NOT perform name-mangling
  885. // on these functions.
  886. extern "C"
  887. {
  888.     extern char     ll_get_drive(void);
  889.     extern int      ll_get_cwd(int, char *);
  890.     extern unsigned int ll_write(int, unsigned int, const void *);
  891. }
  892.  
  893. extern volatile int errno;
  894.  
  895. /*  An empty file object seems silly but here it is anyway */
  896. File::File(void)
  897. {
  898.     handle = -1;
  899.     file_pos = 0L;
  900.     open_flags = 0;
  901.     filelength = 0L;
  902. }
  903.  
  904. /*  This is the File version of the "copy" constructor. it is posible to
  905.     open the file when the object is instantiated by passing TRUE as a
  906.     second parameter.
  907. */
  908. File::File(const File_Spec& original, bool open_file):(original)
  909. {
  910.     handle = -1;
  911.     file_pos = 0L;
  912.     open_flags = 0;
  913.     if ((filelength = filesize(filespec())) == -1L)
  914.         filelength = 0L;
  915.     if (open_file == TRUE)
  916.         open();
  917. }
  918.  
  919. /*  This constructor is the same as the one above. The differences in pass
  920.     parameters are handled by their respective ancestors.
  921. */
  922. File::File(const char *name, bool open_file):(name)
  923. {
  924.     handle = -1;
  925.     file_pos = 0L;
  926.     open_flags = 0;
  927.     if ((filelength = filesize(filespec())) == -1L)
  928.         filelength = 0L;
  929.     if (open_file == TRUE)
  930.         open();
  931. }
  932.  
  933. /*  File destructor */
  934. File::~File(void)
  935. {
  936.     if (handle > 0)
  937.         close();
  938. }
  939.  
  940. /*  Does the file exist? */
  941. bool    File::exists(void)
  942. {
  943.     if (!complete())                      // check for a completed file spec
  944.         return(FALSE);                    // and either fail if not
  945.     if (findfirst(filespec(),0) == NULL)  // or else check for directory entry
  946.         return(FALSE);
  947.     return(TRUE);
  948. }
  949.  
  950. /* Create the file. */
  951. bool    File::create(int mode_flags, bool exclusive)
  952. {
  953.     int     tmp_handle;
  954.     if (!complete())                        // is the file spec complete?
  955.         return(FALSE);
  956.     if (handle > -1)                        // if the file is open
  957.     {
  958.         if (exclusive)
  959.             return(FALSE);
  960.         else
  961.             close();
  962.     }
  963.     else
  964.         if (exists())                       // if the file exists
  965.             if (exclusive)                  // if this flag is TRUE
  966.             {                               // return an error
  967.                 errno = EEXIST;
  968.                 return(FALSE);
  969.             }
  970.     // create the the file and then close it to re-open with the appropriate
  971.     // mode flags set
  972.     if ((tmp_handle = creat(filespec(),S_IWRITE | S_IREAD)) == ERR)
  973.         return(FALSE);
  974.     ::close(tmp_handle);                  // the :: means use the library close
  975.     if (open(mode_flags) == FALSE)          // no :: - use the File method
  976.         return(FALSE);
  977.     set_position(0L);                       // position at the beginning
  978.     filelength = 0L;
  979.     read_only(TRUE);                        // tell File_Spec that it can't
  980.                                             // be changed
  981.     return(TRUE);
  982. }
  983.  
  984. /* Open the file. */
  985. bool    File::open(int mode_flags)
  986. {
  987.     if (handle > -1)                        // if the file is already open
  988.          return(TRUE);                      //      don't re-open it
  989.     if (!complete())                        // check for a complete file spec
  990.         return(FALSE);
  991.     // use the standard library to actually open it ( ::open(...) )
  992.     if ((handle = ::open(filespec(),mode_flags)) == ERR)
  993.         return(FALSE);
  994.     open_flags = mode_flags;                // keep the mode flags
  995.     read_only(TRUE);                        // tell File_Spec not to change a
  996.                                             //      thing
  997.     return(TRUE);
  998. }
  999.  
  1000. /* Close the file. */
  1001. bool    File::close(void)
  1002. {
  1003.     if (handle > -1)                        // if the file's open
  1004.         if (::close(handle) == ERR)         //     close it
  1005.             return(FALSE);
  1006.     handle = -1;                            // and re-initialize everything
  1007.     file_pos = 0L;
  1008.     open_flags = 0;
  1009.     read_only(FALSE);                       // File_Spec can change again
  1010. }
  1011.  
  1012. /*  Read the file. NOTE: bytes actually read may be less than requested.  */
  1013. unsigned int    File::read(void *buffer, unsigned int num_bytes)
  1014. {
  1015.     int     bytes_read;
  1016.     if (handle < 0)                         // make sure the file's open
  1017.     {
  1018.         errno = EBADF;
  1019.         return(FALSE);
  1020.     }
  1021.     // first set the file position. if auto-advance is on set_position()
  1022.     // will just return. otherwise it will move the file pointer to where
  1023.     // it should be. then use the standard read to read the file
  1024.     if (set_position(file_pos) != FALSE)
  1025.         if ((bytes_read = ::read(handle,buffer,num_bytes)) == ERR)
  1026.             return(FALSE);
  1027.     // if auto-advance is on we still need to keep ourselves current
  1028.     if (open_flags & F_ADVANCE)
  1029.         file_pos += (long)bytes_read;
  1030.     // return the number of bytes actually read
  1031.     return(bytes_read);
  1032. }
  1033.  
  1034. /*  Write to the file. In this case failure to write the number of bytes 
  1035.     specified IS considered a failure.
  1036. */
  1037. bool    File::write(const void *buffer, unsigned int num_bytes)
  1038. {
  1039.     if (handle < 0)                        // is the file open?
  1040.     {
  1041.         errno = EBADF;
  1042.         return(FALSE);
  1043.     }
  1044.     if (num_bytes == 0)                  // if zero bytes are to be written
  1045.         return(TRUE);                    // return WITHOUT truncating the file
  1046.     // make sure the file pointer is positioned right and then call our
  1047.     // low level write routine to write it. there's no reason to clutter up
  1048.     // the program with the library write()
  1049.     if (set_position(file_pos) != FALSE)
  1050.         if (ll_write(handle,num_bytes,buffer) < num_bytes)
  1051.         {
  1052.             // at this point we failed to write as many bytes as desired. to
  1053.             // eliminate side effects we truncate
  1054.             truncate();
  1055.             return(FALSE);
  1056.         }
  1057.     // if we wrote at the end of the file, increase its length
  1058.     if (file_pos == filelength)
  1059.         filelength += (unsigned long)num_bytes;
  1060.     if (open_flags & F_ADVANCE)             // check for auto-advance
  1061.         file_pos += (long)num_bytes;
  1062.     return(TRUE);
  1063. }
  1064.  
  1065. /*  Truncate chops a file off at the current file_position. */
  1066. bool    File::truncate(void)
  1067. {
  1068.     if (handle < 0)                         // don't bother if we're not open
  1069.     {
  1070.         errno = EBADF;
  1071.         return(FALSE);
  1072.     }
  1073.     // re-set the file pointer and write zero bytes
  1074.     if (set_position(file_pos) != ERR)
  1075.         if (!ll_write(handle,0,NULL))
  1076.             return(FALSE);
  1077.     filelength = file_pos;                  // re-set the length
  1078.     return(TRUE);
  1079. }
  1080.  
  1081. /*  Position the DOS file pointer */
  1082. bool    File::set_position(long new_file_pos)
  1083. {
  1084.     if (handle < 0)                         // guess!
  1085.     {
  1086.         errno = EBADF;
  1087.         return(FALSE);
  1088.     }
  1089.     // first make sure we're not attempting to set before the beginning or
  1090.     // after the end of the file.
  1091.     if (new_file_pos > filelength || new_file_pos < 0L)
  1092.         return(FALSE);
  1093.     // position it
  1094.     if (lseek(handle,new_file_pos,SEEK_SET) == -1L)
  1095.         return(FALSE);
  1096.     file_pos = new_file_pos;
  1097.     return(TRUE);
  1098. }
  1099.  
  1100. /*  Get the current file position */
  1101. long    File::get_position(void)
  1102. {
  1103.     return(file_pos);
  1104. }
  1105.  
  1106. /*  Get the file size */
  1107. long    File::size(void)
  1108. {
  1109.     long    length;
  1110.     if (handle > -1)
  1111.         return(filelength);
  1112.     else
  1113.     {
  1114.         if (open() == FALSE)
  1115.             return(0L);
  1116.         length = filelength;
  1117.         close();
  1118.         return(length);
  1119.     }
  1120. }
  1121.  
  1122. /*  If we attempt to rename an open file it is first closed and then
  1123.     re-opened after the rename.
  1124.  */
  1125. bool    File::rename(const char *newname)
  1126. {
  1127.     bool    reopen = FALSE;
  1128.     int     tmp_flags;
  1129.     int     i;
  1130.     if (handle > -1)                        // close the file if it's open
  1131.     {
  1132.         tmp_flags = open_flags;
  1133.         close();
  1134.         reopen = TRUE;
  1135.     }
  1136.     else
  1137.         if (exists() == FALSE)              // make sure the file exists
  1138.             return(FALSE);
  1139.     // create a new file spec just like this one (note that it's also
  1140.     // instantiated at this point)
  1141.     File_Spec newspec = *this;
  1142.     newspec.change_name(newname);           // and then change the name
  1143.     if (::rename(filespec(),newspec.filespec()) != 0)
  1144.     {
  1145.         if (reopen)
  1146.             // pass the existing open_flags in case the default wasn't used
  1147.             // when the file was originally opened.
  1148.             open(tmp_flags);
  1149.         return(FALSE);
  1150.     }
  1151.     change_name(newname);                   // now update this file name
  1152.     if (reopen)
  1153.         return(open(tmp_flags));
  1154.     return(TRUE);
  1155. }
  1156.  
  1157. /*  Erase the file. if it's open, close it first. */
  1158. bool    File::erase(void)
  1159. {
  1160.     if (handle > -1)
  1161.         close();
  1162.     if (unlink(filespec()) == ERR)
  1163.         return(FALSE);
  1164.     return(TRUE);
  1165. }
  1166.  
  1167. /*  This might also be a good opportunity for operator overloading.  */
  1168. File    *File::copy(const char *newname, bool overwrite)
  1169. {
  1170.     File            *newfile;               // file to copy to
  1171.     char            *buffer;                // I/O buffer
  1172.     unsigned int    buf_size;               // size of I/O buffer
  1173.     unsigned int    num_bytes;              // number of bytes transfered
  1174.     long            tmp_file_pos;           // temporary file position holder
  1175.     bool            re_close = FALSE;       // flag indicating if source file
  1176.                                             // should be closed following the
  1177.                                             // copy (to avoid side effects)
  1178.     int             tmp_old_flags;          // the original source open flags
  1179.  
  1180.     errno = 0;
  1181.     // first create the new object instance
  1182.     if ((newfile = new File(newname)) == NULL)
  1183.     {
  1184.         errno = ENOMEM;
  1185.         return(NULL);
  1186.     }
  1187.     // then create the new file (invert overwrite for create)
  1188.     if (!(newfile->create(F_ADVANCE | F_RDWR,(bool)!overwrite)))
  1189.     {
  1190.         delete newfile;
  1191.         return(NULL);
  1192.     }
  1193.     // attempt to allocate a buffer. loop until successful or fail at 1 char
  1194.     buf_size = 32768;
  1195.     while ((buffer = new char[buf_size]) == NULL)
  1196.     {
  1197.         buf_size /= 2;
  1198.         if (buf_size == 1)
  1199.         {
  1200.             errno = ENOMEM;
  1201.             delete newfile;
  1202.             return(NULL);
  1203.         }
  1204.     }
  1205.     if (handle < 0)                         // if the source file isn't open
  1206.     {
  1207.         if (!open())                        // open it
  1208.         {
  1209.             newfile->close();               // if we can't open the source
  1210.             newfile->erase();               // file we need to clean up
  1211.             delete newfile;
  1212.             delete[buf_size] buffer;
  1213.             return(NULL);
  1214.         }
  1215.         re_close = TRUE;                    // copy() opened it so copy()
  1216.     }                                       // should close it
  1217.     tmp_old_flags = open_flags;             // keep the original open flags
  1218.     open_flags |= F_ADVANCE;                // and turn auto-advance on
  1219.     tmp_file_pos = file_pos;                // keep the original file pointer
  1220.     set_position(0L);                       // go to the beginning of the file
  1221.     // loop until the entire file has been copied
  1222.     while (num_bytes = read(buffer,buf_size))
  1223.     {
  1224.         if (newfile->write(buffer,num_bytes) == FALSE)
  1225.         {                                   // if a write error occurs we
  1226.             newfile->close();               // need to clean up the mess
  1227.             newfile->erase();               // and return an error
  1228.             delete newfile;
  1229.             delete[buf_size] buffer;
  1230.             open_flags = tmp_old_flags;
  1231.             set_position(tmp_file_pos);
  1232.             if (re_close)
  1233.                 close();
  1234.             return(NULL);
  1235.         }
  1236.     }
  1237.     // clean up and return the new file
  1238.     newfile->close();
  1239.     delete[buf_size] buffer;
  1240.     open_flags = tmp_old_flags;
  1241.     set_position(tmp_file_pos);
  1242.     if (re_close)
  1243.         close();
  1244.     return(newfile);
  1245. }
  1246.  
  1247.  
  1248. [LISTING FIVE]
  1249.  
  1250. ;******************************************************************************
  1251. ;   LOWIO.ASM Written by Kevin D. Weeks Released to the Public Domain
  1252. ;
  1253.  
  1254. include MACROS.ASM                          ; macro file provided by Zortech
  1255.  
  1256. ; import errno
  1257. begdata
  1258.     extrn  _errno:word
  1259. enddata
  1260.  
  1261. ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1262. ;   bool    ll_write(int file_handle, unsigned int num_bytes, void *buffer);
  1263. ;   ll_write simply makes a call to DOS for a write. it varies in two ways
  1264. ;   from the standard C write().
  1265. ;   1. the order of pass parameters (to simplify dealing with 80x86 segments)
  1266. ;   2. it WILL truncate a file
  1267. ;
  1268. begcode ll_write
  1269.     c_public ll_write
  1270. func ll_write
  1271.     push    bp
  1272.     mov     bp,sp
  1273.     push    bx
  1274.     push    cx
  1275.     push    dx
  1276.     push    ds
  1277.  
  1278.     mov     bx,P[bp]                        ; get file handle from stack
  1279.     mov     cx,P[bp + 2]                    ; get number of bytes to write
  1280.     mov     dx,P[bp + 4]                    ; get offset of buffer
  1281. if LPTR                                     ; if large memory model
  1282.     mov     ds,P[bp + 6]                    ; get segment of buffer
  1283. endif
  1284.     mov     ax,4000h                        ; dos write file function
  1285.     int     21h                             ; call dos
  1286.     jc      write_err                       ; carry flag indicates error
  1287.     jmp     write_ret
  1288. write_err:
  1289.     mov     _errno,ax                       ; set errno to error
  1290.  
  1291. write_ret:
  1292.     pop     ds
  1293.     pop     dx
  1294.     pop     cx
  1295.     pop     bx
  1296.     mov     sp,bp
  1297.     pop     bp
  1298.     ret
  1299. c_endp  ll_write
  1300. endcode ll_write
  1301.  
  1302. ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1303. ;   int     ll_get_drive(void);
  1304. ;   ll_get_drive simply returns the current looged disk drive. there is no
  1305. ;   error return.
  1306. ;
  1307. begcode ll_get_drive
  1308.     c_public ll_get_drive
  1309. func ll_get_drive
  1310.     push    bp
  1311.     mov     bp,sp
  1312.     mov     ax,1900h                        ; dos get current drive function
  1313.     int     21h                             ; call dos
  1314.     xor     ah,ah                           ; clear high byte
  1315.     mov     sp,bp
  1316.     pop     bp
  1317.     ret
  1318. c_endp  ll_get_drive
  1319. endcode ll_get_drive
  1320.  
  1321. ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1322. ;   bool    ll_get_cwd(int drive);
  1323. ;   ll_get_cwd gets the current working directory for the specified drive.
  1324. ;   0 means the current drive, 1 means drive A, 2 means drive B, etc. it
  1325. ;   returns a 0 if an error occurs and errno is set.
  1326. ;
  1327. begcode ll_get_cwd
  1328.     c_public ll_get_cwd
  1329. func ll_get_cwd
  1330.     push    bp
  1331.     mov     bp,sp
  1332.     push    dx
  1333.     push    si
  1334.     push    ds
  1335.     mov     dx,P[bp]                        ; get drive
  1336.     mov     si,P[bp + 2]                    ; get offset of buffer
  1337. if LPTR                                     ; if large memory model
  1338.     mov     ds,P[bp + 4]                    ; get segment of buffer
  1339. endif
  1340.     mov     ax,4700h                        ; dos get current cwd function
  1341.     int     21h                             ; call DOS
  1342.     jc      get_cwd_err                     ; carry flag indicates error
  1343.     xor     ax,ax
  1344.     jmp     get_cwd_ret
  1345. get_cwd_err:
  1346.     mov     _errno,ax                       ; set errno to error
  1347.     mov     ax,0ffffh                       ; & set ax to -1
  1348. get_cwd_ret:
  1349.     pop     ds
  1350.     pop     si
  1351.     pop     dx
  1352.     mov     sp,bp
  1353.     pop     bp
  1354.     ret
  1355. c_endp  ll_get_cwd
  1356. endcode ll_get_cwd
  1357.  
  1358. END
  1359.  
  1360.  
  1361.  
  1362. [LISTING SIX]
  1363.  
  1364. /* FILETEST.CPP Written by Kevin D. Weeks Released to the Public Domain  */
  1365.  
  1366. #include <stdio.h>
  1367. #include <errno.h>
  1368. #include <string.h>
  1369. #include "file.hpp"
  1370.  
  1371. // File_Spec test cases
  1372. char    *device_test[] =
  1373. {
  1374.     "",                                     // no device
  1375.     "a:",                                   // complete device
  1376.     ">:",                                   // invalid file char
  1377.     "ab:",                                  // device too long
  1378.     "a::",                                  // invalid char (double colon)
  1379.     NULL
  1380. };
  1381.  
  1382. char    *prefix_test[] =
  1383. {
  1384.   "",                                     // no prefix
  1385.   "\\sub\\",                              // complete prefix
  1386.   "\\>sub\\",                             // complete with invalid file char
  1387.   // prefix too long
  1388.   "\\0123456789\\0123456789\\0123456789\\0123456789\\0123456789\\0123456789\\",
  1389.   "sub.dir\\",                            // incomplete prefix (no backslash)
  1390.   "\\\\sub\\",                            // double back slash
  1391.   "\\sub.dir\\",                          // complete prefix with extension
  1392.   "sub1\\sub2\\",                         // bi-level incomplete prefix
  1393.   "\\sub1\\sub2\\",                       // bi-level conplete prefix
  1394.   "..\\sub",                              // relative, incomplete prefix
  1395.   "..\\sub\\",                            //     "   ,      "       "
  1396.   "..\\",                                 //     "   ,      "       "
  1397.   ".\\sub\\",                             //     "   ,      "       "
  1398.   "\\..\\sub\\",                          // relative but starts at root
  1399.   "..\\>sub\\",                           // relative with invalid file char
  1400.   "\\",                                   // complete prefix
  1401.   NULL
  1402. };
  1403.  
  1404. char    *name_test[] =
  1405. {
  1406.     "",                                     // no name
  1407.     "file.",                                 // complete name
  1408.     "file>.",                               // complete with invalid file char
  1409.     "filetest1.",                           // name too long
  1410.     "file..",                               // invalid char (double dot)
  1411.     "file",                                // incomplete, no dot
  1412.     NULL
  1413. };
  1414.  
  1415. char    *suffix_test[] =
  1416. {
  1417.     "",                                     // no suffix
  1418.     ".tst",                                  // complete suffix
  1419.     ".ts>",                                  // complete with invalid file char
  1420.     ".tst1",                                 // suffix too long
  1421.     NULL
  1422. };
  1423.  
  1424. char    **test[] =
  1425. {
  1426.     device_test,
  1427.     prefix_test,
  1428.     name_test,
  1429.     suffix_test
  1430. };
  1431.  
  1432. char    *test_type[] =
  1433. {
  1434.     "DEVICE ",
  1435.     "PREFIX ",
  1436.     "NAME ",
  1437.     "SUFFIX "
  1438. };
  1439.  
  1440. extern volatile int  errno;
  1441. void    check_file_spec(void);
  1442. void    check_file(void);
  1443. void    make_filespec(char *test_case);
  1444. void    print_condition(File_Spec& file,char *test_name,char *test_case);
  1445.  
  1446. int     main(void)
  1447. {
  1448.     check_file_spec();
  1449.     check_file();
  1450. }
  1451.  
  1452. void    check_file_spec()
  1453. {
  1454.     int     i, j, k;
  1455.     char    test_case[81];
  1456.     char    title[81];
  1457.  
  1458.     printf("\nTESTING File_Spec...\n\n");
  1459.     File_Spec   file1;                      // check void constructor
  1460.     print_condition(file1,"void constructor"," ");
  1461.  
  1462.     for (i = 0; i < 4; i++)
  1463.     {
  1464.         printf("CHECKING %s\n",test_type[i]);
  1465.         j = 0;
  1466.         while (test[i][j] != NULL)
  1467.         {
  1468.             make_filespec(test[i][j]);
  1469.             ++j;
  1470.         }
  1471.     }
  1472.  
  1473.     // check first four complete combinations
  1474.     printf("\nCHECKING COMPLETE FILE SPECS\n");
  1475.     for (i = 0; i < 4; i++)
  1476.     {
  1477.         strcpy(test_case,test[0][i]);
  1478.         strcat(test_case,test[1][i]);
  1479.         strcat(test_case,test[2][i]);
  1480.         strcat(test_case,&test[3][i][1]);
  1481.         make_filespec(test_case);
  1482.     }
  1483.  
  1484.     for (i = 0; i < 4; i++)
  1485.     {
  1486.         printf("CHECKING change_%s\n",test_type[i]);
  1487.         j = 0;
  1488.         while (test[i][j] != NULL)
  1489.         {
  1490.             switch (i)
  1491.             {
  1492.                 case 0:
  1493.                     if (file1.change_device(test[i][j]) == FALSE)
  1494.                         printf("Error changing device");
  1495.                     break;
  1496.                 case 1:
  1497.                     if (file1.change_prefix(test[i][j]) == FALSE)
  1498.                         printf("Error changing prefix");
  1499.                     break;
  1500.                 case 2:
  1501.                     if (file1.change_name(test[i][j]) == FALSE)
  1502.                         printf("Error changing name");
  1503.                     break;
  1504.                 case 3:
  1505.                     if (file1.change_suffix(test[i][j]) == FALSE)
  1506.                         printf("Error changing suffix");
  1507.                     break;
  1508.             };
  1509.             print_condition(file1," ",test[i][j]);
  1510.             ++j;
  1511.         }
  1512.     }
  1513.     printf("\nCOMPLETION TEST\n");
  1514.     file1.change_device();                  // erase current device
  1515.     file1.change_prefix();                  // & prefix
  1516.     print_condition(file1,"BEFORE"," ");
  1517.     file1.complete();
  1518.     print_condition(file1,"AFTER"," ");
  1519.  
  1520.     File_Spec file2 = file1;
  1521.     print_condition(file2,"\nTEST '=' OPERATOR\n","file2 = file1");
  1522.  
  1523. }
  1524.  
  1525. void    make_filespec(char *test_case)
  1526. {
  1527.     File_Spec file2(test_case);
  1528.     print_condition(file2,"char constructor",test_case);
  1529.     File_Spec file3(file2);
  1530.     print_condition(file3,"copy constructor",test_case);
  1531. }
  1532.  
  1533. void    print_condition(File_Spec& file, char *test_name, char *test_case)
  1534. {
  1535.     unsigned int    status;
  1536.     char            *completion;
  1537.     char            *character;
  1538.     static char     incomplete[] = {"INCOMPLETE"};
  1539.     static char     complete[] = {"  complete"};
  1540.     static char     invalid[] = {"INVALID CHAR"};
  1541.     static char     valid[] = {"  chars ok  "};
  1542.  
  1543.     printf("%s\t%s\n",test_name,test_case);
  1544.     status = file.status();
  1545.     printf("file condition: %x\n",status);
  1546.  
  1547.     completion = (status & FLAG_DEVICE) ? incomplete : complete;
  1548.     character = (status & (FLAG_DEVICE << 4)) ? invalid : valid;
  1549.     printf("\tdevice: %-9s\t%s\t%s\n",file.get_device(),completion,character);
  1550.  
  1551.     completion = (status & FLAG_PREFIX) ? incomplete : complete;
  1552.     character = (status & (FLAG_PREFIX << 4)) ? invalid : valid;
  1553.     printf("\tprefix: %-9s\t%s\t%s\n",file.get_prefix(),completion,character);
  1554.  
  1555.     completion = (status & FLAG_NAME) ? incomplete : complete;
  1556.     character = (status & (FLAG_NAME << 4)) ? invalid : valid;
  1557.     printf("\t  name: %-9s\t%s\t%s\n",file.get_name(),completion,character);
  1558.  
  1559.     character = (status & (FLAG_SUFFIX << 4)) ? invalid : valid;
  1560.     printf("\tsuffix: %-9s\t%s\t%s\n",file.get_suffix(),"          ",character);
  1561.  
  1562.     printf("\tfilespec: %s\n\n",file.filespec());
  1563. }
  1564.  
  1565. void    check_file(void)
  1566. {
  1567.     char    buffer[81];
  1568.     int     i;
  1569.  
  1570.     printf("\n\n\nTESTING File...\n\n\n");
  1571.  
  1572.     /* we won't try to perform any constructor tests since most of the
  1573.         attributes are in-accessable and therefore best checked using
  1574.         either a source-level debugger or printf statements. */
  1575.     File file1("file.tst");
  1576.  
  1577.     printf("Creating %s\n",file1.filespec());
  1578.     if (file1.create() == FALSE)
  1579.     {
  1580.         printf("%s already exists. Re-creating it.\n",file1.filespec());
  1581.         if (file1.create(F_RDWR,FALSE) == FALSE)
  1582.         {
  1583.             printf("Error re-creating %s\n",file1.filespec());
  1584.             perror("");
  1585.             return;
  1586.         }
  1587.     }
  1588.     printf("File %s successfully created and opened\n",file1.filespec());
  1589.  
  1590.     strcpy(buffer,"this is a test file");
  1591.     i = strlen(buffer);
  1592.     if (file1.write(buffer,i) == FALSE)
  1593.     {
  1594.         perror("Error writing");
  1595.         printf("Closing file\n");
  1596.         return;
  1597.     }
  1598.     printf("\"%s\" written to file\n",buffer);
  1599.     printf("Current file position is: %ld\n",file1.get_position());
  1600.     printf("Current file length is: %ld\n",file1.size());
  1601.     if (file1.read(buffer,i) == FALSE)
  1602.     {
  1603.         perror("Error reading");
  1604.         printf("Closing file\n");
  1605.         return;
  1606.     }
  1607.     printf("\"%s\" read from file\n",buffer);
  1608.     if (file1.rename("test.fil") == FALSE)
  1609.     {
  1610.         perror("Error renaming");
  1611.         printf("Closing file\n");
  1612.         return;
  1613.     }
  1614.     printf("File renamed to %s\n",file1.filespec());
  1615.  
  1616.     File *file2 = file1.copy("test2.fil");
  1617.     if (errno)
  1618.     {
  1619.         perror("test2.fil");
  1620.         printf("Overwriting it.\n");
  1621.         file2 = file1.copy("test2.fil",TRUE);
  1622.   }
  1623.   if (file2->exists())
  1624.    printf("%s successfully copied to %s\n",file1.filespec(),file2->filespec());
  1625.   else
  1626.   {
  1627.         printf("Copy failed.\n");
  1628.         return;
  1629.     }
  1630.  
  1631.     delete file2;
  1632.  
  1633. }
  1634.  
  1635.